Service (Effect)
特定の機能を凝集したcomponentのこと
実装は隠蔽され、Effect内ではinterfaceを介してアクセスできる
Managing Services | Effect Documentation
Service (Effect)は概念である
以下の組と捉えると良い
①Tag (Effect)
②Service Type
Serviceのinterface
③Serviceのimpl
①と②は同時に宣言される
一つの「①と②」に対して、複数の③が存在しうる
interfaceと実体なのでmrsekut.icon
例
Serviceの作成 (①と②を定義)
code:ts
class Random extends Context.Tag("MyRandomService")<
Random, // ①
{ readonly next: Effect.Effect<number> } // ②
() {}
type RandomTag = Random; // Random
type RandomService = Context.Tag.Service<Random>; // { readonly next: Effect.Effect<number>; }
要はインターフェイスのこと
class名(e.g. Random)の部分を特にTag (Effect)と呼ぶ
Serviceの利用
Tag (Effect)経由で利用する
この時点では実体は確定していない
code:ts
const program = Effect.gen(function* () {
const random = yield* Random
const randomNumber = yield* random.next
console.log(random number: ${randomNumber})
})
Serviceのimplのinjection
③をinjectionする
code:ts
const runnable = Effect.provideService(program, Random, {
next: Effect.sync(() => Math.random())
})
#wip
Serviceとして定義するか否か
どういうものをServiceとして定義するか?
以下のように整理できる
injectionの必要がない (=Serviceを使わない)
①-1 ただのeffect関数群
①-2 一つのinstanceを使いまわしたい
injectionの必要はないが、1つのinstanceを使いまわしたい
e.g. AnthoropicBedrockのinstanceを使いまわしたい
injectionしたい (=Serviceを使う)
interfaceを別途定義しなくて良い
② Effect.Serviceを使って定義
interfaceを別途定義する
③ Context.TagとimplでLayer構築
④ Context.TagとimplでContext構築
そもそも、何でもかんでもinjectionする必要がないmrsekut.icon
injectionしたいモチベーションとしてあり得るのは
ライブラリのインスタンスをシングルトンにしたい時
e.g. アプリケーション全体で、一意のPrisma Clientを使いたい
同一のインターフェイスを用意して、実装を入れ替えたい時
e.g. モックに入れ替えるようなテストを書いている
e.g. 特定のライブラリの依存せず、同一インターフェイスの代替品に入れ替え可能にする
こういう要請がないのに無意味なInterfaceを作るべきでない
Serviceの問題点?
メモ化?
DIPできない?
DIPってそもそもなんで重要なんだっけ
Context (Effect)
Context.Tag.Service<T>
Service Typeを抽出する型
Effect.serviceOption
https://effect.website/docs/requirements-management/services/#optional-services
code:ts
const program = Effect.gen(function* () {
const maybeRandom = yield* Effect.serviceOption(Random)
const randomNumber = Option.isNone(maybeRandom)
? // the service is not available, return a default value
-1
: // the service is available
yield* maybeRandom.value.next
console.log(randomNumber)
})
ないかもしれないService (Effect)を使う時
defaultで入っている5つのServie
https://effect.website/docs/requirements-management/default-services/
code:ts
type DefaultServices = Clock | ConfigProvider | Console | Random | Tracer
例
code:ts
// program :: Effect<void, never, never>
const program = Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis
yield* Console.log(Application started at ${new Date(now)})
})
Effect.runFork(program)
まじかよ、変やろ、お節介すぎるmrsekut.icon
だって型がおかしいやん
上書きもできる
Effect.withClock 特定のサービスを使用してClockエフェクトを実行します。
Effect.withClockScoped Clockサービスを一時的にオーバーライドし、スコープの終了時に復元します。
Effect.withConfigProvider 特定のサービスを使用してConfigProviderエフェクトを実行します。
Effect.withConfigProviderScoped ConfigProviderスコープ内のサービスを一時的にオーバーライドします。
Effect.withConsole 特定のサービスを使用してConsoleエフェクトを実行します。
Effect.withConsoleScoped Consoleスコープ内のサービスを一時的にオーバーライドします。
Effect.withRandom 特定のサービスを使用してRandomエフェクトを実行します。
Effect.withRandomScoped Randomスコープ内のサービスを一時的にオーバーライドします。
Effect.withTracer 特定のサービスを使用してTracerエフェクトを実行します。
Effect.withTracerScoped Tracerスコープ内のサービスを一時的にオーバーライドします。